package org.lib.mysqlhello.security;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;

import org.lib.mysqlhello.security.self.AppUserDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;

@EnableWebSecurity
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {

	// ----试验1----
	
	/**
	 * 试验1：表单认证
	 * 自定义登录页：login.html
	 * 注意参数是 HttpSecurity
	 */
//	@Override
//	protected void configure(HttpSecurity http) throws Exception {
//		http.authorizeRequests()
//				.anyRequest().authenticated()
//				.and()
//			.formLogin()
//				// 自定义登录页
//				.loginPage("/login.html")
//				// 处理登录请求的URL
//				// 指定后登录失败，注释掉，TODO
////				.loginProcessingUrl("/login")
//				// 登录成功的处理
//				.successHandler(new AuthenticationSuccessHandler() {
//					
//					@Override
//					public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
//							Authentication auth) throws IOException, ServletException {
//						resp.setContentType("application/json;charset=utf-8");
//						PrintWriter out = resp.getWriter();
//						out.write(ResultVO.getSuccess("登录成功").toString());
//					}
//				})
//				// 登录失败的处理
//				.failureHandler(new AuthenticationFailureHandler() {
//					
//					@Override
//					public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,
//							AuthenticationException ex) throws IOException, ServletException {
//						resp.setContentType("application/json;charset=utf-8");
//						resp.setStatus(HttpStatus.UNAUTHORIZED.value());
//						PrintWriter out = resp.getWriter();
//						out.write(ResultVO.getFailed(HttpStatus.UNAUTHORIZED.value(), "登录失败", "请重新").toString());
//					}
//				})
//				.permitAll()
//				.and()
//			.csrf().disable();
//	}

	// ----试验2----
	
	/**
	 * 试验2：资源授权
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				// 使用角色
				.antMatchers("/security/admin/**").hasRole("ADMIN")
				.antMatchers("/security/user/**").hasRole("USER")
				.antMatchers("/security/app/**").permitAll()
				// 临时：使用BCryptPasswordEncoder 后，转旧的明文密码
				// 密码还原的时候或可使用
//				.antMatchers("/security/appuser/encode").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin().permitAll()
				.and()
			.csrf().disable();
	}
	
	/**
	 * 基于内存数据库的用户信息
	 */
//	@Bean
//	public UserDetailsService userDetailsService() {
//		// 基于内存的用户信息：2个用户，不同角色
//		InMemoryUserDetailsManager man = new InMemoryUserDetailsManager();
//		man.createUser(User.withUsername("user").password("123").roles("USER").build());
//		man.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
//		return man;
//	}
	
	/**
	 * 数据源
	 */
	@Autowired
	private DataSource dataSource;
	
	/**
	 * 使用JdbcUserDetailsManager
	 * 本应用的底层为 MySQL数据库——上面的dataSource
	 * 
	 * 废弃：使用 AppUserDetailsService
	 */
//	@Bean
	public UserDetailsService userDetailsService() {
		JdbcUserDetailsManager man = new JdbcUserDetailsManager();
		man.setDataSource(dataSource);
		
		if (!man.userExists("user")) {
			man.createUser(User.withUsername("user").password("123").roles("USER").build());
		}
		if (!man.userExists("admin")) {
			man.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
		}
		return man;
	}
	
	/**
	 * 必须有，否则发生异常
	 * 是否可以使用其它 PasswordEncoder 的实现类呢？
	 * 据说是 5.X版本之后默认启用了 委派密码编码器 导致
	 * @author ben
	 * @date 2021-09-05 00:10:49 CST
	 * @return
	 */
//	@Bean
//	public PasswordEncoder passwordEncoder() {
//		// 过时了？怎么弄？TODO
//		// 因为不安全，只能用于测试、明文密码验证等，故废弃
//		return NoOpPasswordEncoder.getInstance();
//	}
	
	/**
	 * BCryptPasswordEncoder
	 * 慢加密
	 * @author ben
	 * @date 2021-09-07 08:20:05 CST
	 * @return
	 */
//	@Bean
//	PasswordEncoder passwordEncoder() {
//		PasswordEncoder encoder = new BCryptPasswordEncoder(12);
//		return encoder;
//	}

	/**
	 * 使用DelegatingPasswordEncoder（1）
	 * @author ben
	 * @date 2021-09-07 11:31:05 CST
	 * @return
	 */
//	@Bean
//	PasswordEncoder passwordEncoder() {
//		String encodingId = "bcrypt";
//		
//		Map<String, PasswordEncoder> encoders = new HashMap<>();
//		// 前面的BCryptPasswordEncoder：使用后，需要给用户密码添加前缀{encodingId}
//		encoders.put(encodingId, new BCryptPasswordEncoder(12));
//		// encoders还可以添加更多
//		
//		return new DelegatingPasswordEncoder(encodingId, encoders);
//	}
	
	/**
	 * 使用DelegatingPasswordEncoder（2）：PasswordEncoderFactories
	 * @author ben
	 * @date 2021-09-07 11:53:07 CST
	 * @return
	 */
//	@Bean
//	PasswordEncoder passwordEncoder() {
//		PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
//		return encoder;
//	}
	
	@Autowired
	private AppUserDAO appUserDao;
	@Autowired
	private HttpServletRequest req;
	
	/**
	 * 使用DelegatingPasswordEncoder（3）：包装UpgradePasswordEncoder2
	 * @author ben
	 * @date 2021-09-07 14:18:22 CST
	 * @return
	 */
	@Bean
	PasswordEncoder passwordEncoder() {
		// 这里还是旧的，但UpgradePasswordEncoder2中使用新的
		String encodingId = UpgradePasswordEncoder2.ENCODER_ID;
		
		Map<String, PasswordEncoder> encoders = new HashMap<>();
		// 前面的BCryptPasswordEncoder：使用后，需要给用户密码添加前缀{encodingId}
//		encoders.put(encodingId, new UpgradePasswordEncoder2(appUserDao, req));
		encoders.put(encodingId, new Pbkdf2PasswordEncoder());
		// encoders还可以添加更多
		
		DelegatingPasswordEncoder encoder = new DelegatingPasswordEncoder(encodingId, encoders);
		
		// 设置升级PasswordEncoder：影响matches
		encoder.setDefaultPasswordEncoderForMatches(new UpgradePasswordEncoder2(appUserDao, req));
		
		return encoder;
	}
	
}
